﻿/* HEADER
 * ------
 * © 2009 by Salomon Zwecker 
 * modified by:
 * - 
 */
using System;
using System.Text;
using Microsoft.Xna.Framework;
using System.Collections.Generic;

namespace Shapes.Geometry
{
    /// <summary>
    /// A rectangular shape
    /// </summary>
    public sealed class Rect : Shape, ILinedShape
    {

        /// <summary>
        /// creates a rectangle shape
        /// </summary>
        /// <param name="width">the width of the rectangle</param>
        /// <param name="height">the height of the rectangle</param>
        public Rect(float width, float height)
            : base()
        {
            ChangeDimension(width, height);
        }
        /// <summary>
        /// creates a rectangle shape
        /// </summary>
        /// <param name="position">the left upper corner of the Rect</param>
        /// <param name="width">the width of the rectangle</param>
        /// <param name="height">the height of the rectangle</param>
        public Rect(Vector2 position, float width, float height)
            : base()
        {
            ChangeDimension(width, height);
            Position = position;
        }

        internal override bool IsPointInside(ref Vector2 point)
        {
            return (point.X >= 0 && point.X <= _Transform._Width
                && point.Y >= 0 && point.Y <= _Transform._Height);

        }

        internal override float GetDistanceToEdge(ref Vector2 point)
        {

            float distTop, distBottom, distLeft, distRight;

            distTop     = Math.Abs(0 - point.Y);
            distBottom  = Math.Abs(_Transform._Height - point.Y);
            distLeft    = Math.Abs(0 - point.X);
            distRight   = Math.Abs(_Transform._Width - point.X);

            // check corners
            if (point.X < 0)
            {
                if (point.Y < 0)
                {
                    return Math.Max(distLeft, distTop);
                }
                if (point.Y > _Transform._Height)
                {
                    return Math.Max(distLeft, distBottom);
                }
                return distLeft;
            }

            if (point.X > _Transform._Width)
            {
                if (point.Y < 0)
                {
                    return Math.Max(distRight, distTop);
                }
                if (point.Y > _Transform._Height)
                {
                    return Math.Max(distRight, distBottom);
                }
                return distRight;
            }

            if (point.Y < 0)
            {
                return distTop;
            }
            if (point.Y > _Transform._Height)
            {
                return distBottom;
            }


            return Math.Min(
                Math.Min(distLeft, distRight),
                Math.Min(distTop, distBottom));
        }

        internal override Vector2 GetNearestPointOnEdge(ref Vector2 point)
        {
            float distTop, distBottom, distLeft, distRight;
            GetDistances(ref point, out distTop, out distBottom, out distLeft, out distRight);
            //distTop     = Math.Abs(0 - point.Y);
            //distBottom  = Math.Abs(_Transform._Height - point.Y);
            //distLeft    = Math.Abs(0 - point.X);
            //distRight   = Math.Abs(_Transform._Width - point.X);

            bool inHorizontalBounds = (point.X >= 0 && point.X <= _Transform._Width);
            bool inVerticalBounds = (point.Y >= 0 && point.Y <= _Transform._Height);
            if (inHorizontalBounds || inVerticalBounds)
            {
                if (inHorizontalBounds && inVerticalBounds) // inside
                {
                    if ((distTop < distLeft && distTop < distRight)
                        || (distBottom < distLeft && distBottom < distRight))
                    {
                        return (distTop < distBottom)
                            ? new Vector2(point.X, 0)
                            : new Vector2(point.X, _Transform._Height);
                    }
                    else
                    {
                        return (distLeft < distRight)
                            ? new Vector2(0, point.Y)
                            : new Vector2(_Transform._Width, point.Y);
                    }
                }
                else if (inHorizontalBounds)
                {
                    return (distTop < distBottom)
                        ? new Vector2(point.X, 0)
                        : new Vector2(point.X, _Transform._Height);
                }
                else
                {
                    return (distLeft < distRight)
                            ? new Vector2(0, point.Y)
                            : new Vector2(_Transform._Width, point.Y);
                }
            }
            else
            {
                Vector2 val = new Vector2();
                val.Y = (distTop < distBottom) ? 0 : _Transform._Height;
                val.X = (distLeft < distRight) ? 0 : _Transform._Width;

                return val;
            }
        }

        internal override Vector2 GetPositionFromT(float t)
        {
            t *= _BorderLength;

            if (t < Width)
            {
                return new Vector2(t, 0);
            }
            else if (t < Width + Height)
            {
                return new Vector2(Width, t - Width);
            }
            else if (t < 2 * Width + Height)
            {
                return new Vector2(Width - (t - Width - Height), Height);
            }
            else
            {
                return new Vector2(0, Height - (t - 2 * Width - Height));
            }
        }

        internal override float GetEdgePathValueFromPoint(ref Vector2 point)
        {
            float distTop, distBottom, distLeft, distRight;
            GetDistances(ref point, out distTop, out distBottom, out distLeft, out distRight);

            float length;
            // top
            if ((distTop < distBottom) && (distTop < distLeft) && (distTop < distRight))
            {
                length = point.X;
            }
            // right
            else if ((distRight < distBottom) && (distRight < distLeft))
            {
                length = _Transform._Width + point.Y;
            }
            // bottom
            else if (distBottom < distLeft)
            {
                length = _Transform._Width + _Transform._Height + (_Transform._Width - point.X);
            }
            // left
            else
            {
                length = 2 * _Transform._Width + _Transform._Height + (_Transform._Height - point.Y);
            }

            return length / _BorderLength;
        }

        private void GetDistances(ref Vector2 point, out float distTop, out float distBottom, out float distLeft, out float distRight)
        {
            distTop = Math.Abs(0 - point.Y);
            distBottom = Math.Abs(_Transform._Height - point.Y);
            distLeft = Math.Abs(0 - point.X);
            distRight = Math.Abs(_Transform._Width - point.X);
        }

        internal override Vector2 GetTangent(ref Vector2 position)
        {
            float top, down, left, right;
            GetDistances(ref position, out top, out down, out left, out right);

            if (top < down && top < left && top < right)
                return new Vector2(1, 0);
            else if (down < left && down < right)
                return new Vector2(-1, 0);
            else if (left < right)
                return new Vector2(0, -1);

            return new Vector2(0, 1);
        }

        internal override Vector2 GetNormal(ref Vector2 position)
        {
            Vector2 tangent = GetTangent(ref position);
            return new Vector2(-tangent.Y, tangent.X);
        }


        /// <summary>
        /// Changes the width and height of the Rect-Shape
        /// </summary>
        /// <param name="width">the new width of the Rect</param>
        /// <param name="height">the new height of the Rect</param>
        public void ChangeDimension(float width, float height)
        {
            _Transform._Width = width;
            _Transform._Height = height;

            _BorderLength = 2 * width + 2 * height;

            CallOnChange();
        }

        /// <summary>
        /// Clones the Rect.
        /// </summary>
        /// <returns>an object with the type Rect</returns>
        public override object Clone()
        {
            Rect rect = new Rect(Width, Height);
            rect._Transform = (Transformation2D)_Transform.Clone();
            return rect;
        }

        /// <summary>
        /// Gets the enumeration type which is mapped to this kind of object
        /// </summary>
        /// <returns>the geometry typee of this object</returns>
        public override GeometryType GetGeometryType()
        {
            return GeometryType.Rect;
        }

        /// <summary>
        /// converts the geometry to a LineStrip.
        /// </summary>
        /// <returns>a line strip object</returns>
        public override LineStrip ToLineStrip()
        {
            return
                new LineStrip(
                    _Transform.TransformLocalToGlobal(new Vector2()),
                    _Transform.TransformLocalToGlobal(new Vector2(0, _Transform.Height)),
                    _Transform.TransformLocalToGlobal(new Vector2(_Transform.Width, _Transform.Height)),
                    _Transform.TransformLocalToGlobal(new Vector2(_Transform.Width, 0))) 
                    { IsClosed = true };
        }

        /// <summary>
        /// Creates Border lines to iterate through
        /// </summary>
        /// <returns>every line of the border of the Rect</returns>
        public IEnumerable<Line> GetLines()
        {
            Vector2 a = _Transform.TransformLocalToGlobal(new Vector2());
            Vector2 b =_Transform.TransformLocalToGlobal(new Vector2(0, _Transform.Height));
            Vector2 c =_Transform.TransformLocalToGlobal(new Vector2(_Transform.Width, _Transform.Height));
            Vector2 d = _Transform.TransformLocalToGlobal(new Vector2(_Transform.Width, 0));

            yield return new Line(a, b);
            yield return new Line(b, c);
            yield return new Line(c, d);
            yield return new Line(d, a);
        }

        /// <summary>
        /// Member of ILinedShape. the startpoint of the first line.
        /// </summary>
        public Vector2 StartPoint
        {
            get { return _Transform.TransformLocalToGlobal(new Vector2()); }
        }

        /// <summary>
        /// Member of ILinedShape. The amount of Lines the Rect has (4).
        /// </summary>
        public int LineCount
        {
            get { return 4; }
        }
    }
}
